﻿using System;
using System.IO;
using Exceptions;

namespace RSA
{
    /// <summary>
    /// Private-key BLOBs, type PRIVATEKEYBLOB, are used to store private keys outside a CSP. Extended provider private-key BLOBs have the following format.
    /// </summary>
    internal struct PrivateKeyBlob
    {
        /// <summary>
        /// BLOB Header
        /// </summary>
        public BlobHeader Header;

        /// <summary>
        /// RSA public key data
        /// </summary>
        public RSAPubKey RSAPubKey;

        /// <summary>
        /// The modulus. This has a value of prime1 * prime2 and is often known as n.
        /// </summary>
        public byte[] Modulus;

        /// <summary>
        /// Prime number 1, often known as p.
        /// </summary>
        public byte[] Prime1;

        /// <summary>
        /// Prime number 2, often known as q.
        /// </summary>
        public byte[] Prime2;

        /// <summary>
        /// Exponent 1. This has a numeric value of d mod (p - 1).
        /// </summary>
        public byte[] Exponent1;

        /// <summary>
        /// Exponent 2. This has a numeric value of d mod (q - 1).
        /// </summary>
        public byte[] Exponent2;

        /// <summary>
        /// Coefficient. This has a numeric value of (inverse of q mod p).
        /// </summary>
        public byte[] Coefficient;

        /// <summary>
        /// Private exponent, often known as d.
        /// </summary>
        public byte[] PrivateExponent;

        #region FromBinary - Create and initialize structure from binary data

        /// <summary>
        /// Create and initialize structure from binary data
        /// </summary>
        /// <exception cref="CryptographicException">On validate errors</exception>
        /// <returns>Initialized structure</returns>
        public static PrivateKeyBlob FromBinary(BinaryReader reader)
        {
            var header = BlobHeader.FromBinary(reader);
            return FromBinary(reader, header);
        }

        /// <summary>
        /// Create and initialize structure from binary data with defined header
        /// </summary>
        /// <exception cref="CryptographicException">On validate errors</exception>
        /// <returns>Initialized structure</returns>
        public static PrivateKeyBlob FromBinary(BinaryReader reader, BlobHeader header)
        {
            var privateKeyBlob = new PrivateKeyBlob
            {
                Header = header,
                RSAPubKey = RSAPubKey.FromBinary(reader),
            };

            int byteLength = (int)(privateKeyBlob.RSAPubKey.BitLength >> 3);
            int wordLength = (int)(privateKeyBlob.RSAPubKey.BitLength >> 4);

            privateKeyBlob.Modulus = new byte[byteLength];
            reader.Read(privateKeyBlob.Modulus, 0, privateKeyBlob.Modulus.Length);

            privateKeyBlob.Prime1 = new byte[wordLength];
            reader.Read(privateKeyBlob.Prime1, 0, privateKeyBlob.Prime1.Length);

            privateKeyBlob.Prime2 = new byte[wordLength];
            reader.Read(privateKeyBlob.Prime2, 0, privateKeyBlob.Prime2.Length);

            privateKeyBlob.Exponent1 = new byte[wordLength];
            reader.Read(privateKeyBlob.Exponent1, 0, privateKeyBlob.Exponent1.Length);

            privateKeyBlob.Exponent2 = new byte[wordLength];
            reader.Read(privateKeyBlob.Exponent2, 0, privateKeyBlob.Exponent2.Length);

            privateKeyBlob.Coefficient = new byte[wordLength];
            reader.Read(privateKeyBlob.Coefficient, 0, privateKeyBlob.Coefficient.Length);

            privateKeyBlob.PrivateExponent = new byte[byteLength];
            reader.Read(privateKeyBlob.PrivateExponent, 0, privateKeyBlob.PrivateExponent.Length);

            return privateKeyBlob;
        }

        #endregion

        #region ToBinary - Serializes structure as binary data

        /// <summary>
        /// Serializes structure as binary data
        /// </summary>
        public void ToBinary(BinaryWriter writer)
        {
            Header.ToBinary(writer);
            RSAPubKey.ToBinary(writer);

            writer.Write(Modulus);
            writer.Write(Prime1);
            writer.Write(Prime2);
            writer.Write(Exponent1);
            writer.Write(Exponent2);
            writer.Write(Coefficient);
            writer.Write(PrivateExponent);
        }

        #endregion

        #region FromRSAParameters - Create and initialize structure from RSAParameters

        /// <summary>
        /// Create and initialize structure from RSAParameters
        /// </summary>
        /// <returns>Initialized structure</returns>
        /// <note>http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsaparameters.aspx</note>
        public static PrivateKeyBlob FromRSAParameters(RSAParameters @params)
        {
            var privateKeyBlob = new PrivateKeyBlob
            {
                Header = BlobHeader.FromRSAParameters(KeyBlobType.PrivateKeyBlob),
                RSAPubKey = RSAPubKey.FromRSAParameters(@params, true),
            };

            privateKeyBlob.Modulus = new byte[@params.N.Length];
            for (int i = 0; i < privateKeyBlob.Modulus.Length; i++)
            {
                privateKeyBlob.Modulus[i] = @params.N[@params.N.Length - i - 1];
            }

            privateKeyBlob.Prime1 = new byte[@params.P.Length];
            for (int i = 0; i < privateKeyBlob.Prime1.Length; i++)
            {
                privateKeyBlob.Prime1[i] = @params.P[@params.P.Length - i - 1];
            }

            privateKeyBlob.Prime2 = new byte[@params.Q.Length];
            for (int i = 0; i < privateKeyBlob.Prime2.Length; i++)
            {
                privateKeyBlob.Prime2[i] = @params.Q[@params.Q.Length - i - 1];
            }

            privateKeyBlob.Exponent1 = new byte[@params.DP.Length];
            for (int i = 0; i < privateKeyBlob.Exponent1.Length; i++)
            {
                privateKeyBlob.Exponent1[i] = @params.DP[@params.DP.Length - i - 1];
            }

            privateKeyBlob.Exponent2 = new byte[@params.DQ.Length];
            for (int i = 0; i < privateKeyBlob.Exponent2.Length; i++)
            {
                privateKeyBlob.Exponent2[i] = @params.DQ[@params.DQ.Length - i - 1];
            }

            privateKeyBlob.Coefficient = new byte[@params.IQ.Length];
            for (int i = 0; i < privateKeyBlob.Coefficient.Length; i++)
            {
                privateKeyBlob.Coefficient[i] = @params.IQ[@params.IQ.Length - i - 1];
            }

            privateKeyBlob.PrivateExponent = new byte[@params.D.Length];
            for (int i = 0; i < privateKeyBlob.PrivateExponent.Length; i++)
            {
                privateKeyBlob.PrivateExponent[i] = @params.D[@params.D.Length - i - 1];
            }

            return privateKeyBlob;
        }

        #endregion

        #region ToRSAParameters - Create and initialize RSAParameters structure

        /// <summary>
        /// Create and initialize RSAParameters structure
        /// </summary>
        /// <returns>Initialized structure</returns>
        /// <note>http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsaparameters.aspx</note>
        public RSAParameters ToRSAParameters()
        {
            var @params = RSAPubKey.ToRSAParameters();

            @params.N = new byte[Modulus.Length];
            for (int i = 0; i < @params.N.Length; i++)
            {
                @params.N[i] = Modulus[Modulus.Length - i - 1];
            }

            @params.P = new byte[Prime1.Length];
            for (int i = 0; i < @params.P.Length; i++)
            {
                @params.P[i] = Prime1[Prime1.Length - i - 1];
            }

            @params.Q = new byte[Prime2.Length];
            for (int i = 0; i < @params.Q.Length; i++)
            {
                @params.Q[i] = Prime2[Prime2.Length - i - 1];
            }

            @params.DP = new byte[Exponent1.Length];
            for (int i = 0; i < @params.DP.Length; i++)
            {
                @params.DP[i] = Exponent1[Exponent1.Length - i - 1];
            }

            @params.DQ = new byte[Exponent2.Length];
            for (int i = 0; i < @params.DQ.Length; i++)
            {
                @params.DQ[i] = Exponent2[Exponent2.Length - i - 1];
            }

            @params.IQ = new byte[Coefficient.Length];
            for (int i = 0; i < @params.IQ.Length; i++)
            {
                @params.IQ[i] = Coefficient[Coefficient.Length - i - 1];
            }

            @params.D = new byte[PrivateExponent.Length];
            for (int i = 0; i < @params.D.Length; i++)
            {
                @params.D[i] = PrivateExponent[PrivateExponent.Length - i - 1];
            }

            return @params;
        }

        #endregion

        #region ToString overriding

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("Header:\r\n{0}\r\nRSAPubKey:\r\n{1}\r\nModulus: {2}", Header, RSAPubKey,
                                 BitConverter.ToString(Modulus));
        }

        #endregion

    }
}
